﻿using System;
using System.Collections.Generic;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

using LinqKit;

namespace Ordinaire.Social.Data
{
    /// <summary>
    /// Class representation of table 'People'.
    /// </summary>
    [Table]
    public class People : Data<int, string>
    {
        #region Variables

        /// <summary>
        /// First name.
        /// </summary>
        private string firstName;

        /// <summary>
        /// Middle name.
        /// </summary>
        private string middleName;

        /// <summary>
        /// Last name.
        /// </summary>
        private string lastName;

        /// <summary>
        /// Date of Birth.
        /// </summary>
        private DateTime? dateOfBirth;

        /// <summary>
        /// Provider name of the data source.
        /// </summary>
        private string providerName;

        /// <summary>
        /// Provider assembly name of the data source.
        /// </summary>
        private string providerAssemblyName;

        /// <summary>
        /// The id of the default address in table "Addresses".
        /// </summary>
        private int defaultAddressesId;

        #endregion

        #region Property methods

        /// <summary>
        /// Get or set the entry unique identifier.
        /// </summary>
        [Column(IsPrimaryKey = true, IsDbGenerated = true, UpdateCheck = UpdateCheck.Never)]
        public override int Id
        {
            get { return base.Id; }
            set { base.Id = value; }
        }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        [Column(Expression = "RTRIM( RTRIM( FirstName + ' ' + ISNULL(MiddleName,'') ) + ' ' + ISNULL(LastName,'') )", UpdateCheck = UpdateCheck.Never)]
        public override string Name
        {
            get { return base.Name; }
            set { base.Name = value; }
        }

        /// <summary>
        /// Gets or sets the first name.
        /// </summary>
        [Column(DbType = "nvarchar(20) NOT NULL", UpdateCheck = UpdateCheck.Never)]
        public string FirstName
        {
            get { return this.firstName; }
            set { this.firstName = value; }
        }

        /// <summary>
        /// Gets or sets the middle name.
        /// </summary>
        [Column(DbType = "nvarchar(20)", UpdateCheck = UpdateCheck.Never)]
        public string MiddleName
        {
            get { return this.middleName; }
            set { this.middleName = value; }
        }

        /// <summary>
        /// Gets or sets the last name.
        /// </summary>
        [Column(DbType = "nvarchar(20)", UpdateCheck = UpdateCheck.Never)]
        public string LastName
        {
            get { return this.lastName; }
            set { this.lastName = value; }
        }

        /// <summary>
        /// Gets or sets the date of birth.
        /// </summary>
        [Column(DbType = "smalldatetime", UpdateCheck = UpdateCheck.Never)]
        public DateTime? DateOfBirth
        {
            get { return this.dateOfBirth; }
            set { this.dateOfBirth = value; }
        }

        /// <summary>
        /// Gets or sets the provider name of the data source.
        /// </summary>
        [Column(CanBeNull = false, DbType = "nvarchar(100) NOT NULL", UpdateCheck = UpdateCheck.Never)]
        public string ProviderName
        {
            get { return this.providerName; }
            set { this.providerName = value; }
        }

        /// <summary>
        /// Gets or sets the provider assembly name of the data source.
        /// </summary>
        [Column(CanBeNull = false, DbType = "nvarchar(max) NOT NULL", UpdateCheck = UpdateCheck.Never)]
        public string ProviderAssemblyName
        {
            get { return this.providerAssemblyName; }
            set { this.providerAssemblyName = value; }
        }

        /// <summary>
        /// Gets or sets the id of the default address in table "Addresses".
        /// </summary>
        [Column(UpdateCheck = UpdateCheck.Never)]
        public int DefaultAddressesId
        {
            get { return this.defaultAddressesId; }
            set { this.defaultAddressesId = value; }
        }

        /// <summary>
        /// Get or set the username who created this entry.
        /// </summary>
        [Column(DbType = "nvarchar(10) NOT NULL", UpdateCheck = UpdateCheck.Never)]
        public override string CreatedBy
        {
            get { return base.CreatedBy; }
            set { base.CreatedBy = value; }
        }

        /// <summary>
        /// Get or set the date when the entry was first created.
        /// </summary>
        [Column(DbType = "datetime", UpdateCheck = UpdateCheck.Never)]
        public override DateTime? CreateDate
        {
            get { return base.CreateDate; }
            set { base.CreateDate = value; }
        }

        /// <summary>
        /// Get or set the username who last updated this entry.
        /// </summary>
        [Column(DbType = "nvarchar(10)", UpdateCheck = UpdateCheck.Never)]
        public override string UpdatedBy
        {
            get { return base.UpdatedBy; }
            set { base.UpdatedBy = value; }
        }

        /// <summary>
        /// Get or set the date when the entry was last updated.
        /// </summary>
        [Column(DbType = "datetime")]
        public override DateTime? UpdateDate
        {
            get { return base.UpdateDate; }
            set { base.UpdateDate = value; }
        }

        #endregion

        #region Expressions

        /// <summary>
        /// Returns a related expression to find data that is identical with the specified first, middle, and last name, as well as the birthdate.
        /// </summary>
        /// <param name="firstName">the first name to search for</param>
        /// <param name="middleName">the middle name to search for</param>
        /// <param name="lastName">the last name to search for</param>
        /// <param name="dateOfBirth">the date of birth of a person to search for</param>
        /// <param name="providerName">the provider name</param>
        /// <returns>related expression</returns>
        public static Expression<Func<People, bool>> AreIdentical(string firstName, string middleName, string lastName, DateTime? dateOfBirth, string providerName = null)
        {
            firstName  = firstName.TrimExtraSpaces().ToLower();
            middleName = middleName.TrimExtraSpaces().ToLower();
            lastName   = lastName.TrimExtraSpaces().ToLower();

            var wherePredicate = PredicateBuilder.True<People>();

            wherePredicate = wherePredicate.And(d => d.FirstName.ToLower() == firstName);

            if (String.IsNullOrEmpty(middleName))
            {
                wherePredicate = wherePredicate.And(d => d.MiddleName == String.Empty || d.MiddleName == null);
            }
            else
            {
                wherePredicate = wherePredicate.And(d => d.MiddleName.ToLower() == middleName);
            }

            if (String.IsNullOrEmpty(lastName))
            {
                wherePredicate = wherePredicate.And(d => d.LastName == String.Empty || d.LastName == null);
            }
            else
            {
                wherePredicate = wherePredicate.And(d => d.LastName.ToLower() == lastName);
            }

            if (dateOfBirth.HasValue)
            {
                wherePredicate = wherePredicate.And(d => d.DateOfBirth.Value == dateOfBirth.Value);
            }
            else
            {
                wherePredicate = wherePredicate.And(d => d.DateOfBirth == null);
            }

            if (!String.IsNullOrEmpty(providerName))
            {
                providerName = providerName.ToLower();
                wherePredicate = wherePredicate.And(d => d.ProviderName.ToLower() == providerName);
            }

            return wherePredicate;
        }

        /// <summary>
        /// Returns a related expression to find data where id is equal to the specified.
        /// </summary>
        /// <param name="id">the id to match with</param>
        /// <returns>related expression</returns>
        public static Expression<Func<People, bool>> IdIs(int id)
        {
            return d => d.Id.Equals(id);
        }

        /// <summary>
        /// Returns a related expression to find data that matches with the specified first and last names.
        /// </summary>
        /// <param name="firstName">the first name</param>
        /// <param name="lastName">the last name</param>
        /// <returns>related expression</returns>
        public static Expression<Func<People, bool>> NamesAre(string firstName, string lastName)
        {
            firstName = firstName.TrimExtraSpaces().ToLower();
            lastName  = lastName.TrimExtraSpaces().ToLower();

            return d => d.FirstName.ToLower() == firstName && d.LastName.ToLower() == lastName;
        }

        /// <summary>
        /// Returns a related expression that retrieves required data and transforms them into an instance of Ordinaire.Social.Person class.
        /// </summary>
        /// <returns>related expression</returns>
        public static Expression<Func<People, Person>> GetPerson()
        {
            return d => d.ToPerson();
        }

        #endregion
    }

    /// <summary>
    /// Contains list of available extension methods associated with Ordinaire.Social.Data.People.
    /// </summary>
    public static class PeopleExtension
    {
        /// <summary>
        /// Converts person's data to a new instance of Ordinaire.Social.Person.
        /// </summary>
        /// <param name="data">data to be converted</param>
        /// <returns>new instance of Ordinaire.Social.Person</returns>
        public static Person ToPerson(this People data)
        {
            if (data == null)
            {
                return null;
            }

            Person instance           = (Person) data.ProviderName.Instance(data.ProviderAssemblyName);
            instance.Id               = data.Id;
            instance.FirstName        = data.FirstName;
            instance.LastName         = data.LastName;
            instance.MiddleName       = data.MiddleName;
            instance.DateOfBirth      = data.DateOfBirth;
            instance.DefaultAddressId = data.DefaultAddressesId;

            return instance;
        }

        /// <summary>
        /// Converts person's data to a new instance of Ordinaire.Social.Person.
        /// </summary>
        /// <typeparam name="TPerson">derived type of Ordinaire.Social.Person</typeparam>
        /// <param name="data">data to be converted</param>
        /// <returns>new instance of specified type</returns>
        public static TPerson ToPerson<TPerson>(this People data)
            where TPerson : Person
        {
            if (data == null)
            {
                return null;
            }

            TPerson instance          = typeof(TPerson).Instance<TPerson>();
            instance.Id               = data.Id;
            instance.FirstName        = data.FirstName;
            instance.LastName         = data.LastName;
            instance.MiddleName       = data.MiddleName;
            instance.DateOfBirth      = data.DateOfBirth;
            instance.DefaultAddressId = data.DefaultAddressesId;

            return instance;
        }
    }
}
